﻿/*********************************************************************************
**                                                                              **
**  Copyright (C) 2024 LiLong                                                   **
**  This file is part of RinexFileReader.                                       **
**                                                                              **
**  RinexFileReader is free software: you can redistribute it and/or modify     **
**  it under the terms of the GNU General Public License as published by        **
**  the Free Software Foundation, either version 3 of the License, or           **
**  (at your option) any later version.                                         **
**                                                                              **
**  RinexFileReader is distributed in the hope that it will be useful,          **
**  but WITHOUT ANY WARRANTY; without even the implied warranty of              **
**  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               **
**  GNU General Public License for more details.                                **
**                                                                              **
**  You should have received a copy of the GNU General Public License           **
**  along with RinexFileReader. If not, see <https://www.gnu.org/licenses/>.    **
**********************************************************************************/
#pragma once

#include "CommonPEG.h"

#include <ranges>

namespace Rinex
{
struct MeteorologicalData
{
    //  IONEX VERSION / TYPE
    double version;
    // PGM / RUN BY / DATE
    std::string program;
    std::string agency;
    std::string createDateTime;
    // *COMMENT
    std::vector<std::string> comments;
    // MARKER NAME
    std::string markerName;
    // *MARKER NUMBER
    std::optional<std::string> markerNumber;
    // # / TYPES OF OBSERV
    std::vector<ObservationType> observationTypes;
    // SENSOR MOD/TYPE/ACC
    std::vector<SensorModTypeAcc> sensorModTypeAcc;
    // SENSOR POS XYZ/H
    std::vector<SensorPosXYZH> sensorPosXYZH;
    //////////////////////////////////
    std::vector<MetData> datas;
};

namespace PEG
{
namespace Meteorological
{
using namespace tao::pegtl;
// clang-format off
struct Version : Float {};
struct Type : one<'M'> {};

struct RINEX_VERSION_TYPE : seq<Space, Version, rep<11, ascii::any>, Type, rep<39, ascii::any>, RINEX_KEYWORDS("RINEX VERSION / TYPE")> {};
struct PGM_RUNBY_DATE : seq<rep<60, ascii::any>, RINEX_KEYWORDS("PGM / RUN BY / DATE")> {};
struct COMMENT : seq<rep<60, ascii::any>, RINEX_KEYWORDS("COMMENT")> {};
struct MARKER_NAME : seq<rep<60, ascii::any>, RINEX_KEYWORDS("MARKER NAME")> {};
struct MARKER_NUMBER : seq<rep<60, ascii::any>, RINEX_KEYWORDS("MARKER NUMBER")> {};
struct TYPES_OF_OBSERV : seq<rep<60, ascii::any>, RINEX_KEYWORDS("# / TYPES OF OBSERV")> {};
struct OBSERVER_AGENCY : seq<rep<60, ascii::any>, RINEX_KEYWORDS("OBSERVER / AGENCY")> {};
struct SENSOR_MOD_TYPE_ACC : seq<rep<60, ascii::any>, RINEX_KEYWORDS("SENSOR MOD/TYPE/ACC")> {};
struct SENSOR_POS_XYZ_H : seq<rep<60, ascii::any>, RINEX_KEYWORDS("SENSOR POS XYZ/H")> {};
struct HeaderEnd : until<RINEX_KEYWORDS("END OF HEADER")> {};

struct HeaderLine : sor<PGM_RUNBY_DATE, COMMENT, MARKER_NAME, MARKER_NUMBER, TYPES_OF_OBSERV, SENSOR_MOD_TYPE_ACC, SENSOR_POS_XYZ_H, OBSERVER_AGENCY> {};
struct Header : seq<RINEX_VERSION_TYPE, plus<HeaderLine>, until<HeaderEnd>> {};
struct MET_DATA {};
// clang-format on
using Rule = seq<Header, plus<MET_DATA>, eof>;

template<typename T>
struct Action
{
};

template<>
struct Action<Version> : DoubleHelper<struct Action<Version>>
{
    static void setValue(MeteorologicalData& data, double value) { data.version = value; }
};

template<>
struct Action<PGM_RUNBY_DATE>
{
    template<typename ParseInput>
    static void apply(const ParseInput& input, MeteorologicalData& data)
    {
        std::string_view str(input.begin(), input.end());
        data.program        = trim(str.substr(0, 20));
        data.agency         = trim(str.substr(20, 20));
        data.createDateTime = trim(str.substr(40, 20));
    }
};

template<>
struct Action<COMMENT>
{
    template<typename ParseInput>
    static void apply(const ParseInput& input, MeteorologicalData& data)
    {
        data.comments.push_back(std::string(trim(std::string_view(input.begin(), input.begin() + 60))));
    }
};

template<>
struct Action<MARKER_NAME>
{
    template<typename ParseInput>
    static void apply(const ParseInput& input, MeteorologicalData& data)
    {
        data.markerName = trim(std::string_view(input.begin(), input.end()).substr(0, 60)); // A60
    }
};

template<>
struct Action<MARKER_NUMBER>
{
    template<typename ParseInput>
    static void apply(const ParseInput& input, MeteorologicalData& data)
    {
        data.markerNumber = trim(std::string_view(input.begin(), input.end()).substr(0, 20)); // A20
    }
};

inline ObservationType toObservationType(std::string_view view)
{
    static constexpr auto list = { "PR", "TD", "HR", "ZW", "ZD", "ZT", "WD", "WS", "RI", "HI" };
    auto it                    = std::ranges::find(list, view);
    if (it == list.end())
        throw std::runtime_error("Unknown ObservationType");
    return static_cast<ObservationType>(std::distance(list.begin(), it));
}

template<>
struct Action<TYPES_OF_OBSERV>
{
    template<typename Rule,
             apply_mode A,
             rewind_mode M,
             template<typename...>
             class Action,
             template<typename...>
             class Control,
             typename ParseInput,
             typename... States>
    [[nodiscard]] static bool match(ParseInput& in, MeteorologicalData& data, States&&... st)
    {
        auto line = in.line_at(in.position());
        if (!line.contains("# / TYPES OF OBSERV"))
            return false;
        auto div       = std::div(stoi(trim(line.substr(0, 6))), 9);
        auto lineCount = div.quot + (div.rem ? 1 : 0);

        for (auto _ : std::views::iota(0, lineCount))
        {
            auto l = in.line_at(in.position());
            for (auto view : l | std::views::drop(6) | std::views::chunk(6)) // I6,9(4X,2A)
            {
                auto str = trim(std::string_view(view.begin(), view.end()));
                if (str.empty())
                    break;
                data.observationTypes.push_back(toObservationType(str));
            }
            in.bump(l.size());
            consumeTail(in);
        }
        return true;
    }
};

template<>
struct Action<SENSOR_MOD_TYPE_ACC>
{
    template<typename ParseInput>
    static void apply(const ParseInput& input, MeteorologicalData& data)
    {
        std::string_view str(input.begin(), input.end());
        SensorModTypeAcc acc;
        acc.model    = trim(str.substr(0, 20));                    // A20
        acc.type     = trim(str.substr(20, 20));                   // A20, 6X
        acc.accuracy = stod(trim(str.substr(46, 7)));              // F7.1, 4X
        acc.obsType  = toObservationType(trim(str.substr(57, 2))); // A2, 1X
        data.sensorModTypeAcc.push_back(std::move(acc));
    }
};

template<>
struct Action<SENSOR_POS_XYZ_H>
{
    template<typename ParseInput>
    static void apply(const ParseInput& input, MeteorologicalData& data)
    {
        std::string_view str(input.begin(), input.end());
        SensorPosXYZH xyzh;
        xyzh.x    = stod(trim(str.substr(0, 14)));              // F14.4
        xyzh.y    = stod(trim(str.substr(14, 14)));             // F14.4
        xyzh.z    = stod(trim(str.substr(28, 14)));             // F14.4
        xyzh.h    = stod(trim(str.substr(42, 14)));             // F14.4
        xyzh.type = toObservationType(trim(str.substr(56, 4))); // 1X, 2A, 1X
        data.sensorPosXYZH.push_back(xyzh);
    }
};

template<>
struct Action<MET_DATA>
{
    template<typename Rule,
             apply_mode A,
             rewind_mode M,
             template<typename...>
             class Action,
             template<typename...>
             class Control,
             typename ParseInput,
             typename... States>
    [[nodiscard]] static bool match(ParseInput& in, MeteorologicalData& data, States&&... st)
    {
        MetData d;
        auto line = in.line_at(in.position());
        if (line.size() < 20)
            return false;
        d.dateTime.msec   = 0;
        d.dateTime.year   = stoi(trim(line.substr(1, 4)));  // 1X, I4.4
        d.dateTime.month  = stoi(trim(line.substr(5, 3)));  // 1X, I2
        d.dateTime.day    = stoi(trim(line.substr(8, 3)));  // 1X, I2
        d.dateTime.hour   = stoi(trim(line.substr(11, 3))); // 1X, I2
        d.dateTime.minute = stoi(trim(line.substr(14, 3))); // 1X, I2
        d.dateTime.second = stoi(trim(line.substr(17, 3))); // 1X, I2
        for (auto view : line | std::views::drop(20) | std::views::chunk(7))
        {
            auto str = trim(std::string_view(view.begin(), view.end()));
            if (str.empty())
                break;
            d.datas.push_back(stoi(str));
        }
        in.bump(line.size());
        consumeTail(in);
        for (;;)
        {
            auto l = in.line_at(in.position());
            if (!l.starts_with("    "))
                break;
            for (auto view : l | std::views::drop(4) | std::views::chunk(7)) // 4X, 10F7.1
            {
                auto str = trim(std::string_view(view.begin(), view.end()));
                if (str.empty())
                    break;
                d.datas.push_back(stoi(str));
            }
            in.bump(l.size());
            consumeTail(in);
        }
        data.datas.push_back(d);
        return !d.datas.empty();
    }
};
} // namespace Meteorological
} // namespace PEG
} // namespace Rinex